//! satp寄存器0x180,S模式下的地址转换与保护寄存器
//!
use crate::{bits, result};
use core::arch::asm;

#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Satp {
    bits: usize,
}
impl Satp {
    pub const BITMASK: usize = usize::MAX;

    pub const fn from_bits(bits: usize) -> Self {
        Self {
            bits: bits & usize::MAX,
        }
    }

    pub const fn bits(&self) -> usize {
        self.bits & usize::MAX
    }

    pub const fn bitmask(&self) -> usize {
        Self::BITMASK
    }
}

#[inline(always)]
unsafe fn _read() -> usize {
    unsafe {
        let r: usize;
        asm!(
            "csrrs {0}, 0x180, zero",
            out(reg)r
        );
        r
    }
}

#[inline]
pub fn read() -> Satp {
    Satp {
        bits: unsafe { _read() },
    }
}

#[inline(always)]
unsafe fn _write(bits: usize) {
    unsafe {
        asm!(
            "csrrw zero, 0x180, {0}",
            in(reg)bits
        );
    }
}

#[inline]
pub fn write(value: Satp) {
    unsafe {
        _write(value.bits);
    }
}

/// 64-bit satp mode
#[repr(usize)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Mode {
    /// No translation or protection
    Bare = 0,
    /// Page-based 39-bit virtual addressing
    Sv39 = 8,
    /// Page-based 48-bit virtual addressing
    Sv48 = 9,
    /// Page-based 57-bit virtual addressing
    Sv57 = 10,
    /// Page-based 64-bit virtual addressing
    Sv64 = 11,
}
impl Mode {
    pub const fn new() -> Self {
        Self::Bare
    }

    pub const fn from_usize(val: usize) -> result::Result<Self> {
        match val {
            0 => Ok(Self::Bare),
            8 => Ok(Self::Sv39),
            9 => Ok(Self::Sv48),
            10 => Ok(Self::Sv57),
            11 => Ok(Self::Sv64),
            _ => Err(result::Error::InvalidVariant(val)),
        }
    }

    pub const fn into_usize(self) -> usize {
        self as usize
    }
}
impl Default for Mode {
    fn default() -> Self {
        Self::new()
    }
}
impl From<Mode> for usize {
    fn from(val: Mode) -> Self {
        val.into_usize()
    }
}
impl TryFrom<usize> for Mode {
    type Error = result::Error;

    fn try_from(val: usize) -> result::Result<Self> {
        Self::from_usize(val)
    }
}

impl Satp {
    /// Physical page number
    #[inline]
    pub fn ppn(&self) -> usize {
        bits::bf_extract(self.bits, 0, 44)
    }

    /// Physical page number
    #[inline]
    pub fn set_ppn(&mut self, set_ppn: usize) {
        self.bits = bits::bf_insert(self.bits, 0, 44, set_ppn);
    }

    /// Address space identifier
    #[inline]
    pub fn asid(&self) -> usize {
        bits::bf_extract(self.bits, 44, 59 - 44 + 1)
    }

    /// Address space identifier
    #[inline]
    pub fn set_asid(&mut self, set_asid: usize) {
        self.bits = bits::bf_insert(self.bits, 44, 59 - 44 + 1, set_asid);
    }

    /// Current address-translation scheme.
    #[inline]
    pub fn mode(&self) -> Mode {
        self.try_mode().unwrap()
    }

    /// Current address-translation scheme.
    #[inline]
    pub fn try_mode(&self) -> result::Result<Mode> {
        let value = bits::bf_extract(self.bits, 60, 63 - 60 + 1);

        Mode::from_usize(value)
    }

    /// Current address-translation scheme.
    #[inline]
    pub fn set_mode(&mut self, set_mode: Mode) {
        self.bits = bits::bf_insert(self.bits, 60, 63 - 60 + 1, set_mode.into());
    }
}

/// Sets the register to corresponding page table mode,
/// physical page number and address space id.
// WARNING: panics on:
// invalid field values
#[inline]
pub fn set(mode: Mode, asid: usize, ppn: usize) {
    unsafe { try_set(mode, asid, ppn).unwrap() }
}

#[inline]
pub unsafe fn try_set(mode: Mode, asid: usize, ppn: usize) -> result::Result<()> {
    if asid != asid & 0xFFFF {
        Err(result::Error::InvalidFieldValue {
            field: "asid",
            value: asid,
            bitmask: 0xFFFF,
        })
    } else if ppn != ppn & 0xFFF_FFFF_FFFF {
        Err(result::Error::InvalidFieldValue {
            field: "ppn",
            value: ppn,
            bitmask: 0xFFF_FFFF_FFFF,
        })
    } else {
        let bits = ((mode as usize) << 60) | (asid << 44) | ppn;
        unsafe { _write(bits) };
        Ok(())
    }
}
